﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Controls.Primitives;
using System.Diagnostics;
using System.Collections.Specialized;

namespace Odyssey.Controls.Primitives
{
    /// <summary>
    /// Used together with ItemPicker control to display an infinite item loop.
    /// </summary>
    public class InfiniteLoopPanel : Panel
    {
        protected struct Range
        {
            public Range(int from, int to)
            {
                From = from;
                To = to;
            }
            public int From;
            public int To;

            public int Count { get { return To - From + 1; } }

            public bool Contains(int value)
            {
                return value >= From && value <= To;
            }

            public static bool operator ==(Range a, Range b)
            {
                return a.From == b.From && a.To == b.To;
            }

            public static bool operator !=(Range a, Range b)
            {
                return a.From != b.From || a.To != b.To;
            }

            public override int GetHashCode()
            {
                return From ^ To;
            }

            public override bool Equals(object obj)
            {
                if (!(obj is Range)) return false;
                return (Range)obj == this;
            }

            public override string ToString()
            {
                return string.Format("{0}-{1}", From, To);
            }

        }

        public InfiniteLoopPanel()
            : base()
        {
            CompositionTarget.Rendering += new EventHandler(OnRendering);
            Loaded += (s, e) => isLoaded = true;
            Unloaded += (s, e) => isLoaded = false;
            SizeChanged += new SizeChangedEventHandler(OnSizeChanged);
        }

        void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            double yOffset = (this.RenderTransform as TranslateTransform).Y;
            Balance(yOffset, true);
            InvalidateMeasure();
        }


        private double currentYOffset = double.NaN;
        private bool isLoaded;
        private bool mustBalance = true;

        /// <summary>
        /// Gets the picker control that hosts this panel.
        /// </summary>
        public IInfiniteLoopHost ItemsHost
        {
            get;
            internal set;
        }

        public static double GetTop(DependencyObject obj)
        {
            return (double)obj.GetValue(TopProperty);
        }

        public static void SetTop(DependencyObject obj, double value)
        {
            obj.SetValue(TopProperty, value);
        }

        public static readonly DependencyProperty TopProperty =
            DependencyProperty.RegisterAttached("Top", typeof(double), typeof(InfiniteLoopPanel), new PropertyMetadata(0.0));


        /// <summary>
        /// Gets whether the panel can perform an infinite loop.
        /// This is the case when the number of items do not fit into the panel.
        /// </summary>
        public bool IsInfinite { get; private set; }

        void OnRendering(object sender, EventArgs e)
        {
            if (isLoaded)
            {
                double yOffset = (this.RenderTransform as TranslateTransform).Y;

                if (currentYOffset != yOffset)
                {
                    currentYOffset = yOffset;
                    Balance(yOffset);
                }
            }
        }

        /// <summary>
        /// Refreshes the layout of the panel.
        /// This is done asynchrounusly in order to minimize the impact on immediately successing Refresh() calls.
        /// </summary>
        public void Refresh()
        {

            // notifies MeasureOverride to also balance the items:
            mustBalance = true;
            InvalidateMeasure();
        }

        protected Range DetermineRange(double yOffset, double height)
        {
            var itemsHost = ItemsHost;

            double itemHeight = itemsHost.ItemHeight;
            double halfHeight = (height - itemHeight) / 2;
            yOffset += halfHeight;
            int topIndex = (int)Math.Floor((-yOffset / itemHeight));
            int bottomIndex = (int)Math.Ceiling((-yOffset + height) / itemHeight);

            return new Range(topIndex, bottomIndex);
        }

        private static int VirtualIndexToDataIndex(int index, int count)
        {
            int dataindex = index % count;
            if (dataindex < 0) dataindex += count;
            return dataindex;
        }

        private Range currentRange;

        private void Balance(double yOffset, bool force = false, double actualHeight = 0)
        {
            var host = ItemsHost;
            double itemHeight = host.ItemHeight;

            int count = host.ItemsSource != null ? host.ItemsSource.Count : 0;
            if (actualHeight == 0) actualHeight = this.ActualHeight;
            if (count > 0 && actualHeight > 0)
            {
                Range range = DetermineRange(yOffset, actualHeight);

                if (force || currentRange.From != range.From)
                {
                    Range previousRange = currentRange;
                    ReleaseItemsOutOfRange(previousRange, range);
                    currentRange = range;
                    IsInfinite = actualHeight < count * itemHeight;

                    double halfHeight = (actualHeight - itemHeight) / 2;

                    int insertIndex = 0;
                    bool added = false;
                    for (int i = range.From; i <= range.To; i++)
                    {
                        int dataIndex = VirtualIndexToDataIndex(i, count);
                        bool isNew = false;
                        ItemPickerItem item = dataIndex >= 0 ? ItemsHost.CreateItem(dataIndex, out isNew) as ItemPickerItem : null;
                        if (item != null)
                        {
                            added = true;
                            if (isNew)
                            {
                                if (i < previousRange.From) Children.Insert(insertIndex++, item); else Children.Add(item);
                                item.Measure(new Size(double.PositiveInfinity, itemHeight));
                                SetTop(item, i * itemHeight + halfHeight);
                            }
                            else
                            {
                                if (force) SetTop(item, i * itemHeight + halfHeight);
                            }
                            ItemsHost.PrepareItem(item);

                        }
                        else
                        {
                            if (!added) currentRange.From++; else currentRange.To--;
                        }
                    }
                }
            }

        }

        private void ReleaseItemsOutOfRange(Range previous, Range current)
        {
            int n = Children.Count;
            if (n > 0 && (previous != current))
            {
                int from = current.From;
                int to = current.To;
                var itemsHost = this.ItemsHost;
                int count = itemsHost.ItemsSource.Count;
                for (int i = n - 1; i >= 0; i--)
                {
                    int index = i + previous.From;
                    if (!current.Contains(index))
                    {
                        Children.RemoveAt(i);
                        int dataIndex = VirtualIndexToDataIndex(index, count);
                        Debug.WriteLine("Removed {0} :  {1}", i, dataIndex);
                        itemsHost.ReleaseItemContainer(dataIndex);
                    }
                }
            }
        }


        protected override Size MeasureOverride(Size availableSize)
        {
            Size size = availableSize;
            if (double.IsInfinity(size.Height)) size.Height = 800.0;
            if (double.IsInfinity(size.Width)) size.Width = 480.0;

            if (mustBalance)
            {
                mustBalance = false;
                double yOffset = (this.RenderTransform as TranslateTransform).Y;
                Balance(yOffset, true);
            }

            //foreach (var child in Children)
            //{
            //  child.Measure(size);
            //}
            return size;
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            var host = ItemsHost;
            double itemHeight = host.ItemHeight;
            foreach (var child in Children)
            {
                double top = GetTop(child);
                child.Arrange(new Rect(0, top, finalSize.Width, itemHeight));
            }
            //ClipRegion(finalSize);

            return finalSize;
        }

        //private void ClipRegion(Size finalSize)
        //{
        //    RectangleGeometry g = this.Clip as RectangleGeometry;
        //    if (g == null)
        //    {
        //        g = new RectangleGeometry();
        //        this.Clip = g;
        //    }
        //    g.Rect = new Rect(0.0, -currentYOffset, finalSize.Width, finalSize.Height);
        //}

        public virtual void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                case NotifyCollectionChangedAction.Remove:
                case NotifyCollectionChangedAction.Reset:
                    RecycleAllItems();
                    Refresh();
                    break;
            }
        }

        private void RecycleAllItems()
        {
            var itemsHost = ItemsHost;
            Children.Clear();
            itemsHost.ClearCache();
            currentRange = new Range(0, -1);
        }
    }
}
